﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace ForceSystem.Untangler.View
{
    public static class ViewHelper
    {
        class TreeLayoutContext
        {
            public Canvas Canvas;
            public Point Position;
            public int NestLevel;

            public readonly Style Style;
            public readonly Style LineStyle;
            public readonly Style IntefaceLabelStyle;
            public readonly Style InterfaceCircleStyle;
            public readonly Style InterfaceLineStyle;

            public readonly double LineAspect;
            public Size IndentSize = new Size(1.8, 0.3);
            public Size Size;
            public readonly Dictionary<string, int> DrawnInterfaces = new Dictionary<string, int>();

            public TreeLayoutContext(Style style, Style lineStyle, Style interfaceLabelStyle, Style interfaceCircleStyle, Style interfaceLineStyle)
            {
                Style = style;
                LineStyle = lineStyle;
                IntefaceLabelStyle = interfaceLabelStyle;
                InterfaceCircleStyle = interfaceCircleStyle;
                InterfaceLineStyle = interfaceLineStyle;
                LineAspect = 0.7;
            }
        }

        static void DrawTreeView( UntangledType baseType, TreeLayoutContext context)
        {
            // Generate label and place current position.
            var label = new Label {Content = baseType.DisplayName, Style = context.Style};
            context.Canvas.Children.Add(label);

            // Dispatch draw to get actual size of control.
            double width = 0, height = 0;
            context.Canvas.Dispatcher.Invoke(() =>
                                                 {
                                                     width = label.ActualWidth;
                                                     height = label.ActualHeight;
                                                 }, DispatcherPriority.Render);

            var iw = context.IndentSize.Width * height;
            var ih = context.IndentSize.Height * height;

            // Draw interface part.
            var interfaces = (from t in baseType.Interfaces
                             where !context.DrawnInterfaces.ContainsKey(t.DisplayName)
                             select t).ToList();

            var drawInterface = interfaces.Any();

            var interfaceCirclePos = new Point();
            if (drawInterface)
            {
                var interfaceCircle = new Ellipse {Style = context.InterfaceCircleStyle};
                context.Canvas.Children.Add(interfaceCircle);

                context.Canvas.Dispatcher.Invoke(() =>
                    {
                        interfaceCirclePos.X = context.Position.X + iw * context.LineAspect - interfaceCircle.ActualWidth;
                        interfaceCirclePos.Y = context.Position.Y;
                        context.Position.Y += interfaceCircle.ActualHeight;
                    }, DispatcherPriority.Render);

                interfaceCircle.RenderTransform = new TranslateTransform(interfaceCirclePos.X, interfaceCirclePos.Y);
                interfaceCirclePos.X += interfaceCircle.ActualWidth * 0.5f;
                interfaceCirclePos.Y += interfaceCircle.ActualHeight;
            }

            foreach (var interfaceType in interfaces)
            {
                var name = interfaceType.DisplayName;

                if (context.DrawnInterfaces.ContainsKey(name))
                {
                    name = interfaceType.FullName;
                }

                context.DrawnInterfaces.Add(name, 1);

                var ifLabel = new Label {Content = name, Style = context.IntefaceLabelStyle};

                context.Canvas.Children.Add(ifLabel);
                ifLabel.RenderTransform = new TranslateTransform(context.Position.X + iw * context.LineAspect, context.Position.Y);
                context.Canvas.Dispatcher.Invoke(() => { context.Position.Y += ifLabel.ActualHeight; },
                                                 DispatcherPriority.Render);
            }

            if (drawInterface)
            {
                context.Position.Y += ih;

                var interfaceLine = new Line {Style = context.InterfaceLineStyle};
                interfaceLine.X1 = interfaceLine.X2 = interfaceCirclePos.X;
                interfaceLine.Y1 = interfaceCirclePos.Y;
                interfaceLine.Y2 = context.Position.Y;
                context.Canvas.Children.Add(interfaceLine);
            }

            // Relocate class name label.
            label.RenderTransform = new TranslateTransform(context.Position.X, context.Position.Y);

            context.Size.Width = Math.Max(context.Position.X + width, context.Size.Width);
            context.Size.Height = Math.Max(context.Position.Y + height, context.Size.Height);

            // Draw horizon line from this node to parent line.
            if (context.NestLevel > 0)
            {
                var line = new Line {Style = context.LineStyle, X1 = context.Position.X - iw*(1 - context.LineAspect)};
                line.Y1 = line.Y2 = context.Position.Y + height * 0.5;
                line.X2 = context.Position.X;
                context.Canvas.Children.Add( line);
            }

            Point linePos0 = context.Position;
            linePos0.X += iw * context.LineAspect;
            linePos0.Y += height;

            context.Position.X += iw;
            context.Position.Y += height + ih;
            context.NestLevel++;

            foreach (var type in baseType.DerivedTypes)
            {
                DrawTreeView(type, context);
            }

            context.NestLevel--;
            context.Position.X -= iw;

            if ( baseType.DerivedTypes.Count > 0 )
            {
                var line = new Line {Style = context.LineStyle};
                line.X1 = line.X2 = linePos0.X;
                line.Y1 = linePos0.Y;
                line.Y2 = context.Position.Y - ih - height * 0.5;
                context.Canvas.Children.Add(line);
            }

            // Make sure label is always top of other drawing.
            context.Canvas.Children.Remove(label);
            context.Canvas.Children.Add(label);
        }

        public static FrameworkElement GenrateDrivedView(Panel parent, UntangledType baseType)
        {
            var outsideOffset = -5000;
            var canvas = new Canvas
                             {
                                 RenderTransform = new TranslateTransform(outsideOffset, 0),
                                 Background= parent.Background,
                             };
            parent.Children.Add(canvas);

            var context = new TreeLayoutContext(
                (Style)parent.FindResource("ClassBox"),
                (Style)parent.FindResource("TreeLine"),
                (Style)parent.FindResource("InterfaceLabel"),
                (Style)parent.FindResource("InterfaceCircle"),
                (Style)parent.FindResource("InterfaceLine")
                ) {Position = {X = 50, Y = 50}, Canvas = canvas};

            DrawTreeView(baseType, context);

            canvas.Dispatcher.Invoke(() =>
                                         {
                                             canvas.RenderTransform = new TranslateTransform(0, 0);
                                             canvas.Width += outsideOffset;
                                         },
                                     DispatcherPriority.Render);

            // Set canvas size.
            canvas.Width = context.Size.Width + 150;
            canvas.Height = context.Size.Height + 150;

            return canvas;
        }


    }
}
